![]() |
![]() |
|
Eine ableitende Klasse, die eine virtuelle Methode der Basisklasse durch eine eigene Implementierung überschreiben möchte, kann die Methodendefinition um den Modifizierer Overrides erweitern:
derselbe Testcode wie oben ausgeführt, ist das Ergebnis ein völlig anderes: Erst wenn es zur Laufzeit zum Aufruf der Methode MyMethod kommt, ermittelt die Laufzeitumgebung den tatsächlichen Typ der Referenz obj und stellt fest, dass es sich um ClassB handelt. Deren Methode wird aufgerufen und gibt im Konsolenfenster
aus. Anscheinend ist das Objekt obj in der Lage zu entscheiden, welche Methode auf sich selbst anzuwenden ist – unabhängig davon, wo es in der Vererbungslinie steht. Diese Fähigkeit wird als Polymorphie bezeichnet und ist neben der Kapselung und der Vererbung die dritte Stütze der objektorientierten Programmierung.
Polymorphie arbeitet mit dynamischer Bindung. Der Aufrufcode wird nicht zur Kompilierzeit erzeugt, sondern erst zur Laufzeit der Anwendung, wenn die konkreten Typinformationen vorliegen, um die tatsächlich aufzurufende Methode zu bestimmen. Im Gegensatz dazu legt die statische Bindung die auszuführende Operation bereits zur Kompilierzeit fest. Beachten Sie, dass eine statische Methode nicht virtuell sein kann. Ebenso ist eine Kombination des Schlüsselworts Overridable mit MustOverride oder Overrides nicht zulässig. Hinter der Definition einer virtuellen Methode verbirgt sich die Absicht, polymorphes Verhalten zu ermöglichen. Daher macht es auch keinen Sinn, ein privates Klassenmitglied Overridable zu deklarieren – es kommt zu einem Kompilierfehler. Shadows und Overrides dürfen nicht für dasselbe Member verwendet werden, sie schließen sich gegenseitig aus. 6.8.2 Inhomogene Mengen
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ' ---------------------------------------------------------- |
| ' Beispiel: ...\Kapitel 6\Polymorphie |
| ' ---------------------------------------------------------- |
| Class Luftfahrzeug |
| Public Overridable Sub Starten() |
| Console.WriteLine("Das Luftfahrzeug startet") |
| End Sub |
| End Class |
| Class Hubschrauber |
| Inherits Luftfahrzeug |
| Public Overrides Sub Starten() |
| Console.WriteLine("Der Hubschrauber startet") |
| End Sub |
| End Class |
| Class Zeppelin |
| Inherits Luftfahrzeug |
| Public Overrides Sub Starten() |
| Console.WriteLine("Der Zeppelin startet") |
| End Sub |
| End Class |
| Class Flugzeug |
| Inherits Luftfahrzeug |
| End Class |
Zeppelin, Hubschrauber und Flugzeug leiten die Klasse Luftfahrzeug ab. Während für ein herkömmliches Flugzeug die Implementierung der Methode in Luftfahrzeug ausreichend ist, überschreiben die beiden anderen Subklassen mit Overrides die geerbte Methode Starten.
Müssen mehrere verschiedene Typen aus einer Vererbungslinie gleichzeitig verwaltet werden, bietet es sich an, dafür ein Array vorzusehen. Sinnvollerweise wird das Array von dem Typ deklariert, welcher der allgemeinste aller verwalteten Objekte ist. In unserem Fall handelt es sich demnach um Luftfahrzeug:
| Dim lfzg(10) As Luftfahrzeug |
Das Array lfzg kann maximal elf Objekte vom Typ Luftfahrzeug aufnehmen, einschließlich der abgeleiteten Typen. In beliebiger Reihenfolge lassen sich den Array-Elementen Referenzen auf die Typen zuweisen. Das Programm enthält eine Methode namens MakeType, in der die Bestimmung des Typs dem Zufallszahlengenerator Random überlassen wird.
| Module Module1 |
| Sub Main() |
| Dim lfzg() As Luftfahrzeug = MakeType() |
| ' Inhalt des Arrays lfzg ausgeben |
| Dim i As Integer |
| For i = 0 To 10 |
| Console.WriteLine("Index = {0} / Typ: {1}", _ |
| i, lfzg(i).ToString()) |
| Next |
| Console.WriteLine("----------------------------------") |
| ' alle Hubschrauber starten |
| For i = 0 To 10 |
| If (TypeOf lfzg(i) Is Hubschrauber) Then |
| Console.Write("Index = {0} / ", i) |
| lfzg(i).Starten() |
| End If |
| Next |
| Console.ReadLine() |
| End Sub |
| ' ein Array mit Objekten vom Typ Luftfahrzeug füllen |
| Public Function MakeType() As Luftfahrzeug() |
| Dim arrLfzg(10) As Luftfahrzeug |
| Dim rnd As New Random |
| Dim type As Integer |
| Dim i As Integer |
| For i = 0 To 10 |
| ' Zufallszahl im Bereich 0 <= type < 3 |
| type = rnd.Next(3) |
| Select Case (type) |
| Case 0 |
| arrLfzg(i) = New Zeppelin() |
| Case 1 |
| arrLfzg(i) = New Hubschrauber() |
| Case 2 |
| arrLfzg(i) = New Flugzeug() |
| End Select |
| Next |
| ' Referenz des Arrays an den Aufrufer liefern |
| Return arrLfzg |
| End Function |
| End Module |
In Main wird der Rückgabewert von MakeType direkt einem Array zugewiesen:
| Dim lfzg() As Luftfahrzeug = MakeType() |
Anschließend werden die Elemente inklusive der Angabe der den Indizes zugeordneten Typen an der Konsole ausgegeben. Um das polymorphe Verhalten zu demonstrieren, werden exemplarisch alle Hubschrauber gestartet. Dazu muss zuerst der Typ, der sich hinter jedem Array-Element verbirgt, mit
| If (TypeOf lfzg(i) Is Hubschrauber) Then |
ermittelt werden. Liefert die Typbestimmung True, wird mit
| lfzg(i).Starten() |
der Hubschrauber gestartet.
Durch die Definition Overridable in der Basisklasse gestehen wir der Methode polymorphes Verhalten zu. Unabhängig davon, ob sich hinter einem Array-Element, das vom Typ der Basisklasse Luftfahrzeug ist, tatsächlich ein Flugzeug, Hubschrauber oder Zeppelin verbirgt, wird der Aufruf immer polymorph an die »richtige« Methode weitergeleitet.
Um polymorphes Verhalten zu ermöglichen, muss eine Methode Overridable definiert sein. In einer abgeleiteten Klasse kann dieses Angebot angenommen oder abgelehnt werden.
| Implementieren Sie in der abgeleiteten Klasse die geerbte Methode mit dem Modifizierer Overrides, wird die ursprüngliche Methode überschrieben – die abgeleitete Klasse akzeptiert das Angebot der Basisklasse. Ein Aufruf an eine Referenz des Oberbegriffs wird polymorph an den sich tatsächlich dahinter verbergenden Typ weitergeleitet. |
| In der abgeleiteten Klasse kann eine virtuelle Methode auch mit dem Modifizierer Shadows ausgeblendet werden. Dann verdeckt die geerbte Methode in der Subklasse die Implementierung in der Basisklasse und zeigt kein polymorphes Verhalten mehr. |
Es ist sogar möglich, innerhalb einer Vererbungskette ein gemischtes Verhalten von Ausblendung und Überschreibung vorzusehen, wie das folgende Codefragment zeigt:
| Class ClassA |
| Public Overridable Sub TestMethod() |
| Console.WriteLine("MyMethod in ClassA") |
| End Sub |
| End Class |
| Class ClassB |
| Inherits ClassA |
| Public Overrides Sub TestMethod() |
| Console.WriteLine("MyMethod in ClassB") |
| End Sub |
| End Class |
| Class ClassC |
| Inherits ClassA |
| Public Shadows Sub TestMethod() |
| Console.WriteLine("MyMethod in ClassB") |
| End Sub |
| End Class |
ClassA bietet die virtuelle Methode TestMethod an. ClassB als Subklasse von ClassA überschreibt mit Overrides, die zweite Ableitung in der ClassC verdeckt jedoch nur noch mit Shadows.
Wenn Sie nun nach der Zuweisung
| Dim obj As ClassA = New ClassC() |
auf die Referenz obj die Methode TestMethod aufrufen, wird die Methode TestMethod in ClassB ausgeführt, da diese die aus ClassA geerbte polymorph überschreibt. TestMethod zeigt aber in der Klasse ClassC wegen des Modifizierers Shadows kein polymorphes Verhalten mehr.
Das Überschreiben einer mit Shadows überdeckenden Methode mit Overrides ist nicht möglich:
| Class ClassB |
| Inherits ClassA |
| Public Shadows Sub TestMethod() |
| Console.WriteLine("MyMethod in ClassB") |
| End Sub |
| End Class |
| Class ClassC |
| Inherits ClassB |
| Public Overrides Sub TestMethod() |
| Console.WriteLine("MyMethod in ClassB") |
| End Sub |
| End Class |
Der VB-Compiler wird nun eine Fehlermeldung ausgeben.
Die Klasse Object, aus der jede Klasse des .NET Frameworks abgeleitet wird, vererbt jeder Klasse eine Reihe elementarer Methoden. Eine davon, ToString, haben wir in unserem Beispiel Polymorphie dazu benutzt, um im Befehlsfenster den Typ, der sich hinter einem Array-Element verbirgt, ausgeben zu lassen.
Vielleicht gefällt uns der Rückgabewert der Standardimplementierung von ToString nicht, der immer den voll qualifizierenden Namen liefert. ToString ist ein typischer Kandidat zum polymorphen Überschreiben, um eine typspezifisch angepasste Zeichenfolge zu erhalten. In der Praxis wird das tatsächlich auch häufig gemacht. In der .NET-Dokumentation ist die Definition dieser Methode wie folgt angegeben:
| Public Overridable Function ToString As String |
Überschrieben werden sollte ToString in jeder Klasse mit Overrides, um das polymorphe Verhalten durchzusetzen. Stellvertretend für die Klassen Flugzeug, Zeppelin und Hubschrauber sei an dieser Stelle nur die Ergänzung der Klasse Hubschrauber gezeigt:
| Class Hubschrauber |
| Inherits Luftfahrzeug |
| ... |
| Public Overrides Function ToString() As String |
| Return "Hubschrauber" |
| End Function |
| End Class |
Jede Methode einer Basisklasse wird an die abgeleitete Klasse vererbt. Ist die Methode darüber hinaus virtuell definiert, sollten Sie das als ein Angebot der Basisklasse verstehen, das Sie annehmen oder ablehnen können. Nehmen Sie das Angebot wahr, überschreiben Sie die geerbte Methode mit Overrides und setzen damit polymorphes Verhalten durch. Sie können das Angebot auch ablehnen und dennoch die geerbte Methode in der abgeleiteten Klasse neu implementieren. Dazu gibt es den Modifizierer Shadows, der aber bekanntlich nicht nur auf geerbte, virtuelle Methoden eingesetzt werden kann. Es gibt nur relativ wenig Methoden, die das Angebot virtueller Methoden auf diese Weise ausschlagen.
Ob Sie eine Methode in einer ableitbaren Klasse virtuell zur Verfügung stellen oder wie Sie auf das Angebot in der Subklasse reagieren, kann im Einzelfall unterschiedlich sein und muss eingehend geprüft werden.
Standardmäßig können Klassen abgeleitet werden. Ist dieses Verhalten für eine bestimmte Klasse nicht gewünscht, kann die Klasse mit NotInheritable als nicht ableitbar definiert werden.
In ähnlicher Weise können Sie dem weiteren Überschreiben einer Methode auch einen Riegel vorschieben, indem die Definition der Methode um den Modifizierer NotOverridable ergänzt wird:
| Class ClassB |
| Inherits ClassA |
| Public NotOverridable Sub TestMethod() |
| Console.WriteLine("TestProc in ClassB") |
| End Sub |
| End Class |
Eine von ClassB abgeleitete Klasse erbt zwar die Methode, kann sie aber selbst nicht mit Overrides überschreiben. Es ist jedoch möglich, in einer abgeleiteten Klasse eine geerbte, nicht-überschreibbare Methode mit Shadows zu überdecken, um eine typspezifische Anpassung vornehmen zu können.
Mit drei spezifischen Referenzen lässt sich der Aufruf von Methoden innerhalb einer Vererbungshierarchie steuern: Me, MyBase und MyClass. Die beiden erstgenannten haben wir in den vorhergehenden Abschnitten bereits besprochen, das soll aber an dieser Stelle noch einmal kurz wiederholt werden:
Me liefert die Referenz auf die Klasse, in der sich der ausgeführte Code momentan befindet. Es ist praktisch der Zeiger eines Objekts auf sich selbst und kann nur im Zusammenhang mit Instanz-Membern verwendet werden.
MyBase hingegen wird verwendet, wenn aus einer abgeleiteten Klasse heraus auf ein Member der direkten Basisklasse zugegriffen werden soll. Wir hatten diese Referenz unter anderem im Zusammenhang mit der Konstruktorverkettung im Einsatz erlebt.
Ganz neu in Visual Basic 2005 ist die dritte oben aufgeführte Referenz MyClass. Deren Wirkungsweise sehen wir uns am besten in einem kleinen Beispiel an.
| Module Module1 |
| Sub Main() |
| Dim testObj As New SubClass |
| ' Aufruf von TestMethod mit 'Me' |
| testObj.MethodWithMe() |
| ' Aufruf von TestMethod mit 'MyClass' |
| testObj.MethodWithMyClass() |
| End Sub |
| End Module |
| ' Basisklasse |
| Class BaseClass |
| Public Overridable Sub TestMethod() |
| Console.WriteLine("In der Basisklasse") |
| End Sub |
| Public Sub MethodWithMe() |
| Me.TestMethod() |
| End Sub |
| Public Sub MethodWithMyClass() |
| MyClass.TestMethod() |
| End Sub |
| End Class |
| ' abgeleitete Klasse |
| Class SubClass |
| Inherits BaseClass |
| Public Overrides Sub TestMethod() |
| Console.WriteLine("In der abgeleiteten Klasse") |
| End Sub |
| End Class |
Eine Basisklasse und deren Ableitung bilden den Kern dieses Beispiels. Von der Basisklasse wird die Methode TestMethod bereitgestellt, die von der abgeleiteten Klasse polymorph überschrieben wird. Zudem sind die beiden Methoden MethodWithMe und MethodWithMyClass in der Basisklasse codiert und werden 1:1 von der Subklasse geerbt.
In Main wird die Subklasse instanziiert. Auf das Objekt wird die Method MethodWithMe aufgerufen, in der wiederum auf die Me-Referenz TestMethod aufgerufen wird. Me bezieht sich auf das aktuelle Objekt, und das ist vom Typ der abgeleiteten Klasse. Folglich wird auch die überschriebene Methode TestMethod in der Subklasse ausgeführt.
Ein ganz anderes Verhalten zeigt sich, wenn die Methode MethodWithMyClass aufgerufen wird. Nun kommt es zur Ausführung von TestMethod in der Basisklasse, die Überschreibung in der aktuellen wird außer Kraft gesetzt. Genau das ist auch der Unterschied im Vergleich zu Me und MyBase. Während Me sich auf das aktuelle Objekt bezieht und mit MyBase die Polymorphie immer noch durchgesetzt wird, wird Letztere mit MyClass ausgeschaltet. Es ist so, als wäre die Methode TestMethod in der Basisklasse NotOverridable deklariert.
| Methoden ohne Anweisungsblock legen kein Objektverhalten fest und werden als abstrakte Methoden bezeichnet. Abstrakte Methoden werden mit dem Modifizierer Must-Override gekennzeichnet und haben keinen Programmcode. Statische (Shared) Methoden können niemals abstrakt sein. |
| Klassen, die mindestens eine abstrakte Methode haben, sind selbst abstrakt und müssen mit dem Modifizierer MustInherit signiert werden. Eine Klasse kann auch dann abstrakt sein, wenn keines ihrer Mitglieder abstrakt ist. |
| Abstrakte Klassen können nicht instanziiert werden. Deshalb machen abstrakte Klassendefinitionen nur dann Sinn, wenn sie abgeleitet werden. |
| Eine nichtabstrakte Klasse, die von einer abstrakten Klasse abgeleitet wird, muss alle geerbten abstrakten Methoden implementieren. |
| Eine geerbte abstrakte Methode wird in der abgeleiteten Klasse durch eine Signatur, die den Overrides-Modifizierer verwendet, überschrieben. Wird die geerbte abstrakte Methode nicht überschrieben, gilt auch die abgeleitete Klasse als abstrakt. Überschriebene abstrakte Methoden zeigen immer polymorhes Verhalten. |
| Polymorphie ist einer der Stützpfeiler der objektorientierten Programmierung. Polymorphie bedeutet, dass Objekte bei gleichen Methodenaufrufen unterschiedlich reagieren können, wenn die Objekte unter einem gemeinsamen Oberbegriff betrachtet werden. |
| Eine Methode, die sich polymorph verhalten soll, muss entweder MustOverride oder Overridable deklariert sein. Polymorphe Methodenaufrufe werden dynamisch zur Laufzeit gebunden, wenn in der abgeleiteten Klasse die virtuelle Methode mit Overrides überschrieben wird. Wird eine virtuelle Methode in der Subklasse mit Shadows ausgeblendet, setzt sich das polymorphe Verhalten nicht durch, und der Methodenaufruf wird statisch gebunden. |
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken.
Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die
gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich
geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung,
Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.